//  GREKO Chess Engine
//  (c) 2002-2012 Vladimir Medvedev <vrm@bk.ru>
//  http://greko.110mb.com

//  book.cpp: opening book
//  modified: 05-Aug-2012

#include "book.h"
#include "notation.h"
#include "utils.h"

struct MoveAndValue
{
  MoveAndValue(Move mv, int value) : _mv(mv), _value(value) {}
  ~MoveAndValue() {}

  bool operator< (const MoveAndValue& m) const
  { 
    return _value > m._value; // reverse order!
  }

  Move _mv;
  int  _value;
};

Move Book::GetMove(const Position& pos, std::string& comment)
{
  comment.clear();

  // check to avoid lines like 1. e4 e5 2. Nf3 a6 3. Bb5 Nc6?
  if (_data.find(pos.Hash()) == _data.end()) return 0;

  _pos = pos;
  std::vector<MoveAndValue> x;

  MoveList mvlist;
  mvlist.GenAllMoves(_pos);

  int sumVal = 0;
  for (int i = 0; i < mvlist.Size(); ++i)
  {
    Move mv = mvlist[i]._mv;
    if (_pos.MakeMove(mv))
    {
      std::map<U64, int>::const_iterator it = _data.find(_pos.Hash());
      if (it != _data.end() && it->second > 0)
      {
        x.push_back(MoveAndValue(mv, it->second));
        sumVal += it->second;
      }
      _pos.UnmakeMove();
    }
  }

  if (sumVal > 0)
  {
    sort(x.begin(), x.end());
    for (size_t i = 0; i < x.size(); ++i)
    {
      char buf[256];
      sprintf(buf, "%s %d%c", MoveToStrShort(x[i]._mv, _pos).c_str(), 100 * x[i]._value / sumVal, '%');
      comment += buf;
      if (i != x.size() - 1)
        comment += ", ";      
    }

    int N = int(Rand64() % sumVal);
    for (size_t i = 0; i < x.size(); ++i)
    {
      N -= x[i]._value;
      if (N <= 0)
        return x[i]._mv;
    }
  }

  return 0;
}

bool Book::Import(const std::string& strPath, const std::string& strMaxPly, const std::string& strColor)
{ 
  FILE* src = fopen(strPath.c_str(), "rt");
  if (!src)
  {
    out("can't open %s\n", strPath);
    return false;
  }

  int maxPly = strMaxPly.empty() ? 20 : atoi(strMaxPly.c_str());
  out("maxPly = %d\n", maxPly);

  bool addColor[2] = {true, true};
  if (strColor.size() > 0)
  {
    if (strColor[0] == 'w')
    {
      addColor[BLACK] = false;
      out("white's moves only\n");
    }
    else if (strColor[0] == 'b')
    {
      addColor[WHITE] = false;
      out("black's moves only\n");
    }
  }
  
  Position startpos;
  startpos.SetInitial();

  int nGames = 0;
  char buf[4096];
  while (fgets(buf, sizeof(buf), src))
  {
    if (strlen(buf) < 2)
      continue;
    if (buf[0] == '[')
      continue;

    TokenString s(buf);
    for (std::string token = s.GetToken(); token.length() > 0; token = s.GetToken())
    {
      if (token == "1." || token == "1")
      {
        _pos = startpos;
        ++nGames;
        printf("Games: %d, nodes: %d\r", nGames, _data.size());
        continue;
      }

      if (_pos.Ply() >= maxPly)
        continue;

      Move mv = StrToMove(token, _pos);
      if (mv)
      {
        _pos.MakeMove(mv);

        if (addColor[_pos.Side() ^ 1])
          ++_data[_pos.Hash()];
        else
          _data[_pos.Hash()] += 0;
      }
    }
  }

  _data.insert(std::pair<U64, int>(startpos.Hash(), 1));

  printf("Games: %d, nodes: %d\n", nGames, _data.size());
  fclose(src);
  return true;
}

void Book::Init()
{
  Clean();

  if (Load("book.bin"))
    ;
  else
  { 
    FILE* src = fopen("book.txt", "rt");
    if (src)
    {
      out("reading book.txt...\n");
      char buf[256];
      while (fgets(buf, sizeof(buf), src))
      {
        _pos.SetInitial();
        ProcessLine(buf);
      }
      fclose (src);
      out("book.txt: %d nodes\n", _data.size());
    }
    else
    {
      out("book.txt not found\n");
    }
  } 
}

bool Book::Load(const std::string& path)
{
  FILE* srcBin = fopen(path.c_str(), "rb");
  if (srcBin)
  {
    U64 hash;
    int be;

    while (fread(&hash, sizeof(U64), 1, srcBin))
    {
      fread(&be, sizeof(int), 1, srcBin);
      _data[hash] += be;
    }
    fclose(srcBin);
    out("%s: %d nodes\n", path.c_str(), _data.size());
    return true;
  }
  else
  {
    out("can't open %s\n", path);
    return false;
  }
}

void Book::ProcessLine(const std::string& str)
{
  TokenString s(str);
  for (std::string token = s.GetToken(); token.length() > 0; token = s.GetToken())
  {
    Move mv = StrToMove(token, _pos);
    if (mv)
    {
      _pos.MakeMove(mv);
      ++_data[_pos.Hash()];
    }
  }
}

bool Book::Save(const std::string& path)
{
  FILE* dest = fopen(path.c_str(), "wb");
  if (dest)
  {
    if (!_data.empty())
    {
      out("writing %s...\n", path);
    }
    for (std::map<U64, int>::const_iterator it = _data.begin(); it != _data.end(); ++it)
    {
      U64 hash = it->first;
      int be = it->second;
      fwrite(&hash, sizeof(U64), 1, dest);
      fwrite(&be, sizeof(int), 1, dest);
    }
    fclose(dest);
    return true;
  }

  return false;
}
